package com.xuhuan.client.util;

import com.alibaba.fastjson2.JSONObject;
import com.xuhuan.client.business.splitfile.dto.SplitFileResult;
import com.xuhuan.client.business.splitfile.dto.ZoneInfo;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @Author huan.xu
 * @Date 2023/6/17 21:07
 * @Description 文件拆分以及合并工具类
 */
@Slf4j
public class FileSplitUtill {

    private FileSplitUtill() {
    }

    private static int THOUSAND_TWENTY_FOUR = 1024;

    /**
     * 把大文件拆分为多个小文件
     *
     * @param sourceFilePath 需要被拆分的文件路径
     * @param targetDirPath  拆分后的文件应该存放的文件路径
     * @param partitionSize 单个文件大小，单位M
     */
    public static SplitFileResult splitFile(String sourceFilePath, String targetDirPath,int partitionSize) throws Exception {
        String uuid = UuidUtil.uuid();
        return splitFile(sourceFilePath, targetDirPath + File.separator + uuid, uuid,partitionSize);
    }

    /**
     * 把大文件拆分为多个小文件
     *
     * @param sourceFilePath 需要被拆分的文件路径
     * @param targetDirPath  拆分后的文件应该存放的文件路径
     * @param zoneProfixName 每一个小文件名前缀
     */
    public static SplitFileResult splitFile(String sourceFilePath, String targetDirPath, String zoneProfixName,int partitionSize) throws Exception {
        log.info("开始文件切片，要分片的文件地址：{}，分片结果目录：{}", sourceFilePath, targetDirPath);

        File sourceFile = new File(sourceFilePath);
        if (!sourceFile.exists()) {
            throw new RuntimeException("要切片的文件不存在");
        }
        File targetDir = new File(targetDirPath);
        if (!targetDir.exists()) {
            targetDir.mkdirs();
        }

        FileInputStream fis = new FileInputStream(sourceFile);
        BufferedInputStream bis = new BufferedInputStream(fis);

        Integer zoneSize = 0;
        for (int i = 0; ; i++) {
            String filePath = targetDirPath + File.separatorChar + i + "_" + zoneProfixName;
            boolean flag = singleSplitFile(bis, filePath, partitionSize);
            zoneSize++;
            if (flag) {
                break;
            }
        }
        log.info("文件切片完成，总片数：{}", zoneSize);
        fis.close();
        bis.close();
        //组装返回信息
        SplitFileResult result = new SplitFileResult();
        result.setZoneSize(zoneSize);
        result.setZoneProfixName(zoneProfixName);
        List<ZoneInfo> zoneInfoList = FileHandleUtil.handleDirMd5(targetDir);

        List<String> zoneMd5List = zoneInfoList.stream()
                .map(zoneInfo -> zoneInfo.getZoneMd5())
                .collect(Collectors.toList());
        result.setZoneInfoList(zoneInfoList);
        result.setFileMd5(FileHandleUtil.md5List(zoneMd5List));

        return result;
    }


    /**
     * @param bis           输入流
     * @param filePath      新文件存储的文件夹路径
     * @param partitionSize 每个文件的大小 单位MB
     * @throws IOException
     */
    private static boolean singleSplitFile(BufferedInputStream bis, String filePath, int partitionSize) throws Exception {
        int length = partitionSize * THOUSAND_TWENTY_FOUR * THOUSAND_TWENTY_FOUR;
        byte[] buffer = new byte[THOUSAND_TWENTY_FOUR];
        int tempLength;

        FileOutputStream fos = new FileOutputStream(filePath);
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        while ((tempLength = bis.read(buffer)) != -1) {
            bos.write(buffer, 0, tempLength);
            if ((length = length - tempLength) <= 0) {
                break;
            }
        }
        bos.close();
        fos.close();

        return tempLength < 0;
    }

    /**
     * 把指定文件夹内的文件合并为一个文件
     *
     * @param sourcePath 需要被合并的文件所在的文件中路径
     * @param desPath    合并后的文件存放位置
     */
    public static void combineFile(String sourcePath, String desPath) throws Exception {
        List<String> sortedFileNames = sortFilesName(sourcePath);
        if (sortedFileNames == null) {
            throw new RuntimeException(sourcePath + "目录下不存在文件");
        }

        FileOutputStream fos = new FileOutputStream(desPath);
        BufferedOutputStream bos = new BufferedOutputStream(fos);

        log.info("开始合并文件");
        for (String sortedFileName : sortedFileNames) {
            String filePath = sourcePath + File.separatorChar + sortedFileName;
            copyFile(bos, filePath);
            new File(filePath).delete();
        }
        log.info("文件合并完成");

        bos.close();
        fos.close();
    }

    /**
     * 把目标文件合并到输出流中
     *
     * @param bos 缓冲输出流
     * @param des 目标文件路径
     * @throws IOException
     */
    private static void copyFile(BufferedOutputStream bos, String des) throws Exception {
        FileInputStream fis = new FileInputStream(des);
        BufferedInputStream bis = new BufferedInputStream(fis);

        log.info("------>>>>[{}]开始合并", des);
        byte[] bytes = new byte[THOUSAND_TWENTY_FOUR];
        int length;
        while ((length = bis.read(bytes)) != -1) {
            bos.write(bytes, 0, length);
        }

        bis.close();
        fis.close();
    }

    /**
     * 对指定文件夹内的文件名进行排序 （增序）
     *
     * @param fileDirPath
     * @return
     */
    private static List<String> sortFilesName(String fileDirPath) {
        File dir = new File(fileDirPath);
        File[] fileList = dir.listFiles();
        if (null == fileList || fileList.length == 0) {
            return null;
        }
        Pattern compile = Pattern.compile("^.+_\\d+$");
        return Arrays.stream(fileList)
                .filter(file -> {
                    if (file.isDirectory()) {
                        log.warn("waring:{}不是一个文件", file.getName());
                        return false;
                    }
                    String fileName = file.getName();
                    Matcher matcher = compile.matcher(fileName);
                    if (!matcher.find()) {
                        log.warn("waring:文件名{}不符合文件名_数字的命名规范", fileName);
                        return false;
                    }
                    return true;
                })
                .map(File::getName)
                .sorted((fileName1, fileName2) -> {
                    String[] split1 = fileName1.split("_");
                    String[] split2 = fileName2.split("_");
                    return Integer.parseInt(split1[split1.length - 1]) - Integer.parseInt(split2[split2.length - 1]);
                }).collect(Collectors.toList());
    }

    public static void main(String[] args) throws Exception {
        String sourceFilePath = "C:\\Users\\Administrator\\Desktop\\file-split\\aims-client.zip";
        String targetDirPath = "C:\\Users\\Administrator\\Desktop\\file-split\\test";

        SplitFileResult result = FileSplitUtill.splitFile(sourceFilePath, targetDirPath,1);
        log.info("切片结果：{}", JSONObject.toJSONString(result));

//        String targetFilePath = "C:\\Users\\Administrator\\Desktop\\file-split\\pp4.jpg";
//        FileSplitUtill.combineFile(targetDirPath, targetFilePath);
    }
}
